package cn.chenc.framework.jpa.repository;

import cn.chenc.framework.core.text.Convert;
import cn.chenc.framework.core.util.AssertUtil;
import cn.chenc.framework.jpa.exception.RepositoryException;
import cn.chenc.framework.jpa.model.PageInfo;
import cn.chenc.framework.jpa.util.SqlHelper;
import jakarta.persistence.EntityManager;
import jakarta.persistence.PersistenceContext;
import jakarta.persistence.Query;
import org.hibernate.Session;
import org.hibernate.internal.SessionImpl;
import org.hibernate.query.sql.internal.NativeQueryImpl;
import org.hibernate.transform.Transformers;
import org.springframework.dao.EmptyResultDataAccessException;
import org.springframework.data.domain.Example;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.support.JpaEntityInformation;
import org.springframework.data.jpa.repository.support.SimpleJpaRepository;
import org.springframework.data.repository.NoRepositoryBean;
import org.springframework.transaction.annotation.Transactional;

import java.io.Serializable;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.util.*;

/**
 * 　@description: BaseRepository 实现
 * 　@author secret
 * 　@date 2021/6/5 23:14
 *
 */
@NoRepositoryBean
@Transactional
public class BaseRepositoryImpl <T, ID extends Serializable> extends SimpleJpaRepository<T, ID>
        implements BaseRepository<T, ID> {

    @PersistenceContext
    private final EntityManager entityManager;
    private Class<T> clazz;
    private JpaEntityInformation<T, ?> entityInformation;

    public BaseRepositoryImpl(JpaEntityInformation<T, ?> entityInformation, Class<T> domainClass, EntityManager entityManager) {
        super(domainClass, entityManager);
        this.clazz = domainClass;
        this.entityManager = entityManager;
        this.entityInformation = entityInformation;
    }

    @Override
    public T get(ID id) throws RepositoryException {
        AssertUtil.notNull(id, "get() id must not be null!");
        T t = this.entityManager.find(this.clazz, id);
        if (this.entityManager != null) {
            this.entityManager.close();
        }

        return t;
    }

    @Override
    @Modifying
    public T update(T t) throws RepositoryException {
        AssertUtil.notNull(t, "modify() entity must not be null!");
        Session session = (Session)this.entityManager.unwrap(Session.class);
        T entity = (T) session.merge(t);
        if (this.entityManager != null) {
            this.entityManager.close();
        }

        return entity;
    }

    @Override
    public T saveOrUpdate(ID id, T t) throws RepositoryException {
        AssertUtil.notNull(id, "id must not be null");
        AssertUtil.notNull(t, "entity must not be null");
        if (id != null) {
            T entity = this.get(id);
            if (entity != null) {
                return this.update(t);
            }
        }

        return this.save(t);
    }

    @Override
    public void delete(ID id) throws RepositoryException {
        AssertUtil.notNull(id, "id must not be null");
        T entity = this.get(id);
        if (entity == null) {
            String msg = String.format("No %s entity with id %s exists!", this.entityInformation.getJavaType(), id);
            throw new EmptyResultDataAccessException(msg, 1);
        } else {
            this.deleteById(id);
        }
    }

    @Override
    public void deleteBatchByIds(Collection<ID> ids) throws RepositoryException {
        AssertUtil.notNull(ids, "ids must not be null");
        Iterator var2 = ids.iterator();

        while(var2.hasNext()) {
            ID id = (ID) var2.next();
            this.deleteById(id);
        }

    }

    @Override
    public T getOneByObject(T t) throws RepositoryException {
        T entity = null;
        Example example = Example.of(t);

        try {
            Optional<T> optional = this.findOne(example);
            if (optional.isPresent()) {
                entity = optional.get();
            }

            return entity;
        } catch (Exception var5) {
            throw new RepositoryException("Problem invoking method, Cause:" + var5.getMessage(), var5);
        }
    }

    @Override
    public List<T> getListByObject(T t) throws RepositoryException {
        Example<T> example = Example.of(t);
        return this.findAll(example);
    }

    @Override
    public List<T> getListByObject(T t, Sort sort) throws RepositoryException {
        Example<T> example = Example.of(t);
        return this.findAll(example, sort);
    }

    @Override
    public Page<T> getListByObject(T t, Pageable pageable) throws RepositoryException {
        Example<T> example = Example.of(t);
        return this.findAll(example, pageable);
    }

    @Override
    public List<T> getListBySql(String sql) throws RepositoryException {
        return this.getListBySql(sql, this.clazz);
    }

    @Override
    public List<T> getListBySql(String sql, Class<T> clzss) throws RepositoryException {
        AssertUtil.notNull(sql, "getListBySql() sql and object must not be null!");
        AssertUtil.notNull(clzss, "getListBySql() clzss must not be null!");
        Query query = this.entityManager.createNativeQuery(sql, clzss);
        List<T> list = query.getResultList();
        if (this.entityManager != null) {
            this.entityManager.close();
        }
        return list;
    }

    @Override
    public PageInfo getListBySql(String sql, PageInfo pageable) throws RepositoryException {
        return this.getListBySql(sql, pageable, (Map)null);
    }


    @Override
    public PageInfo getListBySql(String sql, PageInfo pageable, Map<String, Object> searchMap) throws RepositoryException {
        Query query = this.entityManager.createNativeQuery(sql, this.clazz);
        if (null != searchMap) {
            Iterator var5 = searchMap.entrySet().iterator();

            while(var5.hasNext()) {
                Map.Entry<String, Object> entry = (Map.Entry)var5.next();
                query.setParameter((String)entry.getKey(), entry.getValue());
            }
        }

        int pageNum = pageable.getPageNum();
        int pageSize = pageable.getPageSize();
        if (pageNum >= 1 && pageSize >= 1) {
            int currPageCount = PageInfo.offsetCurrent(pageNum, pageSize);
            int totalCount = this.getCountBySql(sql, searchMap);
            query.setFirstResult(currPageCount).setMaxResults(pageSize);
            List<Map<String, Object>> list = query.getResultList();
            PageInfo pageUtil = new PageInfo(pageNum, pageSize, totalCount);
            pageUtil.setCurrPageCount(list.size());
            pageUtil.setContent(list);
            return pageUtil;
        } else {
            throw new IllegalArgumentException("pageNum and pageSize must more than 0");
        }
    }

    @Override
    public List<Map<String, Object>> getListMapBySql(String sql) throws RepositoryException {
        return this.getListMapBySql(sql, (Map) new HashMap<>());
    }

    @Override
    public List<Map<String, Object>> getListMapBySql(String sql, Map<String, Object> searchMap) throws RepositoryException {
        AssertUtil.notNull(sql, "getListMapBySql() sql must not be null!");
        Query query = this.entityManager.createNativeQuery(sql);
        if (null != searchMap) {
            Iterator var4 = searchMap.entrySet().iterator();

            while(var4.hasNext()) {
                Map.Entry<String, Object> entry = (Map.Entry)var4.next();
                query.setParameter((String)entry.getKey(), entry.getValue());
            }
        }

        ((NativeQueryImpl)query.unwrap(NativeQueryImpl.class)).setResultTransformer(Transformers.ALIAS_TO_ENTITY_MAP);
        List<Map<String, Object>> list = query.getResultList();
        if (this.entityManager != null) {
            this.entityManager.close();
        }

        return list;
    }

    @Override
    public PageInfo getListMapBySql(String sql, PageInfo pageable) throws RepositoryException {
        return this.getListMapBySql(sql, pageable, (Map)null);
    }

    @Override
    public PageInfo getListMapBySql(String sql, PageInfo pageable, Map<String, Object> searchMap) throws RepositoryException {
        Query query = this.entityManager.createNativeQuery(sql);
        if (null != searchMap) {
            Iterator var5 = searchMap.entrySet().iterator();

            while(var5.hasNext()) {
                Map.Entry<String, Object> entry = (Map.Entry)var5.next();
                query.setParameter((String)entry.getKey(), entry.getValue());
            }
        }

        int pageNum = pageable.getPageNum();
        int pageSize = pageable.getPageSize();
        if (pageNum >= 1 && pageSize >= 1) {
            int currPageCount = PageInfo.offsetCurrent(pageNum, pageSize);
            int totalCount = this.getCountBySql(sql, searchMap);
            query.setFirstResult(currPageCount).setMaxResults(pageSize);
            ((NativeQueryImpl)query.unwrap(NativeQueryImpl.class)).setResultTransformer(Transformers.ALIAS_TO_ENTITY_MAP);
            List<Map<String, Object>> list = query.getResultList();
            PageInfo pageUtil = new PageInfo(pageNum, pageSize, totalCount);
            pageUtil.setCurrPageCount(list.size());
            pageUtil.setContent(list);
            return pageUtil;
        } else {
            throw new IllegalArgumentException("pageNum and pageSize must more than 0");
        }
    }

    @Override
    public Map<String, Object> getSingleMapBySql(String sql) throws RepositoryException {
        AssertUtil.notNull(sql, "getSingleMapBySql() sql must not be null!");
        List<Map<String, Object>> list = this.getListMapBySql(sql);
        Map<String, Object> singleMap = new HashMap();
        if (list.size() > 0) {
            singleMap = (Map)list.get(0);
        }

        return (Map)singleMap;
    }

    @Override
    public int insertBySql(String sql) throws RepositoryException {
        AssertUtil.notNull(sql, "insertBySql() sql must not be null!");
        Query query = this.entityManager.createNativeQuery(sql);
        int count = query.executeUpdate();
        if (this.entityManager != null) {
            this.entityManager.close();
        }

        return count;
    }

    @Override
    public int insertBySql(String sql, Map<String, Object> paraMap) throws RepositoryException {
        AssertUtil.notNull(sql, "insertBySql() sql must not be null!");
        Query query = this.entityManager.createNativeQuery(sql);
        if (null != paraMap) {
            Iterator var4 = paraMap.entrySet().iterator();

            while(var4.hasNext()) {
                Map.Entry<String, Object> entry = (Map.Entry)var4.next();
                query.setParameter((String)entry.getKey(), entry.getValue());
            }
        }

        int count = query.executeUpdate();
        if (this.entityManager != null) {
            this.entityManager.close();
        }

        return count;
    }

    @Override
    public int updateBySql(String sql) throws RepositoryException {
        AssertUtil.notNull(sql, "updateBySql() sql must not be null!");
        Query query = this.entityManager.createNativeQuery(sql);
        int count = query.executeUpdate();
        if (this.entityManager != null) {
            this.entityManager.close();
        }

        return count;
    }

    @Override
    public int deleteBySql(String sql) throws RepositoryException {
        AssertUtil.notNull(sql, "deleteBySql() sql must not be null!");
        Query query = this.entityManager.createNativeQuery(sql);
        int count = query.executeUpdate();
        if (this.entityManager != null) {
            this.entityManager.close();
        }

        return count;
    }

    @Override
    public int executeBySql(String sql) throws RepositoryException {
        AssertUtil.notNull(sql, "executeBySql() sql must not be null!");
        Query query = this.entityManager.createNativeQuery(sql);
        int cout = query.executeUpdate();
        if (this.entityManager != null) {
            this.entityManager.close();
        }

        return cout;
    }

    @Override
    public int executeBySql(List<String> sqls) throws RepositoryException {
        AssertUtil.notNull(sqls, "executeBySql() sql must not be null!");
        int i = 0;

        int count;
        for(Iterator var3 = sqls.iterator(); var3.hasNext(); i += count) {
            String sql = (String)var3.next();
            Query query = this.entityManager.createNativeQuery(sql);
            count = query.executeUpdate();
        }

        if (this.entityManager != null) {
            this.entityManager.close();
        }
        return i;
    }

    @Override
    public int getCountBySql(String sql, Map<String, Object> searchMap) throws RepositoryException {
        AssertUtil.notNull(sql, "getCountBySql() sql must not be null!");
        String countSql = SqlHelper.queryCountSQL(sql);
        Query query = this.entityManager.createNativeQuery(countSql);
        if (null != searchMap) {
            Iterator var5 = searchMap.entrySet().iterator();

            while(var5.hasNext()) {
                Map.Entry<String, Object> entry = (Map.Entry)var5.next();
                query.setParameter((String)entry.getKey(), entry.getValue());
            }
        }

        List totals = query.getResultList();
        if (this.entityManager != null) {
            this.entityManager.close();
        }
        return totals.size() > 0 ? Convert.toInt(totals.get(0)) : 0;
    }

    @Override
    public List<Map<String, String>> getColumnsAndTypeBySql(String sql) throws RepositoryException {
        AssertUtil.notNull(sql, "getColumnsBySql() sql must not be null!");
        Connection connection = null;
        ArrayList strList = new ArrayList();

        try {
            connection = ((SessionImpl)this.entityManager.unwrap(SessionImpl.class)).getJdbcConnectionAccess().obtainConnection();
            ResultSet rs = connection.prepareStatement(sql).executeQuery();
            ResultSetMetaData rsmd = rs.getMetaData();
            int count = rsmd.getColumnCount();
            Map<String, Integer> strMap = new HashMap();

            for(int i = 0; i < count; ++i) {
                Map<String, String> map = new HashMap();
                String columnName = rsmd.getColumnName(i + 1).toLowerCase();
                if (strMap.get(columnName) == null) {
                    strMap.put(columnName, 1);
                } else {
                    int number = (Integer)strMap.get(columnName);
                    columnName = columnName + number;
                    ++number;
                    strMap.put(columnName, number);
                }

                map.put("columnName", columnName);
                map.put("columnType", rsmd.getColumnTypeName(i + 1));
                strList.add(map);
            }
        } catch (Exception var15) {
            throw new RepositoryException(var15.getMessage(), var15);
        } finally {
            this.entityManager.close();
        }

        return strList;
    }

    @Override
    public List<String> getColumnsBySql(String sql) throws RepositoryException {
        AssertUtil.notNull(sql, "getColumnsBySql() sql must not be null!");
        Connection connection = null;
        ArrayList strList = new ArrayList();

        try {
            connection = ((SessionImpl)this.entityManager.unwrap(SessionImpl.class)).getJdbcConnectionAccess().obtainConnection();
            ResultSet rs = connection.prepareStatement(sql).executeQuery();
            ResultSetMetaData rsmd = rs.getMetaData();
            int count = rsmd.getColumnCount();
            Map<String, Integer> strMap = new HashMap();

            for(int i = 0; i < count; ++i) {
                String columnName = rsmd.getColumnName(i + 1).toLowerCase();
                if (strMap.get(columnName) == null) {
                    strMap.put(columnName, 1);
                } else {
                    int number = (Integer)strMap.get(columnName);
                    columnName = columnName + number;
                    ++number;
                    strMap.put(columnName, number);
                }

                strList.add(columnName);
            }
        } catch (SQLException var15) {
            throw new RepositoryException("SQL配置有误");
        } catch (Exception var16) {
            throw new RepositoryException(var16.getMessage(), var16);
        } finally {
            this.entityManager.close();
        }

        return strList;
    }

    @Override
    public List<String> getColumnsByTableName(String tableName) throws RepositoryException {
        AssertUtil.notNull(tableName, "getColumnsBySql() sql must not be null!");
        Connection connection = null;
        ArrayList strList = new ArrayList();

        try {
            connection = ((SessionImpl)this.entityManager.unwrap(SessionImpl.class)).getJdbcConnectionAccess().obtainConnection();
            ResultSet rs = connection.prepareStatement("show full columns from " + tableName).executeQuery();

            while(rs.next()) {
                strList.add(rs.getString("Field"));
            }
        } catch (SQLException var9) {
            throw new RepositoryException("TableName配置有误");
        } catch (Exception var10) {
            throw new RepositoryException(var10.getMessage(), var10);
        } finally {
            this.entityManager.close();
        }

        return strList;
    }

}
